
/* GCSx
** LOAD.CPP
**
** File loading only
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

// Read interface

FileRead::FileRead(File* myFilePtr, int offset, int size, Uint32 myVersion, Uint32 myCompression) throw_File { start_func
    filePtr = myFilePtr;
    currentOffset = startOffset = offset;
    bytesRead = 0;
    totalSize = size;
    version = myVersion;
    compression = myCompression;
    zlibBufferIn = NULL;
    zlibStream = NULL;
    zlibStreamInit = 0;
    
    try {
        if (compression == WorldFileLoad::BLOCKCOMPRESSION_ZLIB) {
            // Read original size and checksum
            if (size < 4) {
                throw FileException("Unexpected end-of-block");
            }
            filePtr->seek(currentOffset);
            zlibOrigSize = filePtr->readInt();
            bytesRead += 4;
            currentOffset += 4;

            // Prepare objects
            zlibBufferIn = new Byte[ZLIB_BUFFER_SIZE];
            zlibStream = new z_stream;
            
            // Get first block NOW, so we are most likely to read zlib header here
            zlibStream->avail_in = 0;
            zlibNextBuffer();
            
            // Use default alloc and free for now
            zlibStream->zalloc = NULL;
            zlibStream->zfree = NULL;
            zlibStream->opaque = NULL;
            if (inflateInit(zlibStream) != Z_OK) {
                fatalCrash(0, "ZLIB Memory/decompression error: %s", zlibStream->msg);
            }
            zlibStreamInit = 1;
        }
    }
    catch (...) {
        delete[] zlibBufferIn;
        if (zlibStreamInit) inflateEnd(zlibStream);
        delete zlibStream;
        throw;
    }
}

FileRead::~FileRead() { start_func
    delete[] zlibBufferIn;
    if (zlibStreamInit) inflateEnd(zlibStream);
    delete zlibStream;
}

void FileRead::zlibNextBuffer() throw_File { start_func
    assert(compression == WorldFileLoad::BLOCKCOMPRESSION_ZLIB);
    assert(zlibStream);
    assert(zlibBufferIn);
    assert(zlibStream->avail_in == 0);

    int amount = min(totalSize - bytesRead, (int)ZLIB_BUFFER_SIZE);
    if (amount == 0) {
        throw FileException("Unexpected end-of-block");
    }

    filePtr->seek(currentOffset);
    filePtr->read(zlibBufferIn, amount);
    zlibStream->next_in = zlibBufferIn;
    zlibStream->avail_in = amount;
    bytesRead += amount;
    currentOffset += amount;
}

int FileRead::getSize() const { start_func
    if (compression == WorldFileLoad::BLOCKCOMPRESSION_ZLIB) {
        return zlibOrigSize;
    }
    else {
        return totalSize;
    }
}

int FileRead::moreData() const { start_func
    if (compression == WorldFileLoad::BLOCKCOMPRESSION_ZLIB) {
        return zlibOrigSize - zlibStream->total_out;
    }
    else {
        return totalSize - bytesRead;
    }
}

void FileRead::read(void* ptr, int size) throw_File { start_func
    if (compression == WorldFileLoad::BLOCKCOMPRESSION_ZLIB) {
        zlibStream->next_out = (Byte*)ptr;
        zlibStream->avail_out = size;
        
        // Inflate until error, reading more buffer as needed
        while (zlibStream->avail_out) {
            // Need more input buffer? (this also checks if we overflow)
            if (zlibStream->avail_in == 0) zlibNextBuffer();
            int result = inflate(zlibStream, 0);
            if (result == Z_STREAM_END) {
                // End of stream- we'd better be done!
                if (zlibStream->avail_out) {
                    throw FileException("Unexpected end of compressed data");
                }
                // We are, at least with this read; mark us as done, unless we rewind
                bytesRead = totalSize;
            }
            // OK/BUF_ERROR means we need or may need to read more buffer
            else if ((result != Z_OK) && (result != Z_BUF_ERROR)) {
                throw FileException("Decompression error: %s", zlibStream->msg);
            }
        }
    }
    else {
        if (bytesRead + size > totalSize) {
            throw FileException("Unexpected end-of-block");
        }
        
        filePtr->seek(currentOffset);
        filePtr->read(ptr, size);
        
        bytesRead += size;
        currentOffset += size;
    }
}

char FileRead::copyBuffer[COPY_BUFFER_SIZE + 1];

void FileRead::readStr(string& str) throw_File { start_func
    int size = readInt16();
    int first = 1;
    
    if (size) {
        while (size) {
            int amount = min(size, (int)COPY_BUFFER_SIZE);
            read(copyBuffer, amount);
            copyBuffer[amount] = 0;
            if (first) str = copyBuffer;
            else str += copyBuffer;
            size -= amount;
        }
    }
    else {
        str = blankString;
    }
}

void FileRead::readIntBulk(Uint32* ptr, int count) throw_File { start_func
    read(ptr, 4 * count);
#if SDL_BYTEORDER != SDL_LIL_ENDIAN
    for (int pos = 0; pos < count; ++pos) {
        Uint8* bytes = ptr++;
        swap(bytes[0], bytes[3]);
        swap(bytes[1], bytes[2]);
    }
#endif
}

Uint32 FileRead::readInt() throw_File { start_func
    Uint32 result;
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
    read(&result, 4);
#else
    Uint8 bytes[4];
    read(bytes, 4);
    result = (Uint32)bytes[0] | ((Uint32)bytes[1] << 8) | ((Uint32)bytes[2] << 16) | ((Uint32)bytes[1] << 24);
#endif
    
    return result;
}

Uint16 FileRead::readInt16() throw_File { start_func
    Uint16 result;
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
    read(&result, 2);
#else
    Uint8 bytes[2];
    read(bytes, 2);
    result = (Uint16)bytes[0] | ((Uint16)bytes[1] << 8);
#endif
    
    return result;
}

Uint8 FileRead::readInt8() throw_File { start_func
    Uint8 result;
    read(&result, 1);
    return result;
}

void FileRead::rewind() throw_File { start_func
    if (compression == WorldFileLoad::BLOCKCOMPRESSION_ZLIB) {
        // Optimization- don't do anything if we're already rewound
        if (bytesRead - zlibStream->avail_in != 4) {
            // End current stream
            inflateEnd(zlibStream);
            zlibStreamInit = 0;
    
            // Back to beginning, AFTER stored size
            currentOffset = startOffset + 4;
            bytesRead = 4;
    
            // Reget first block
            zlibStream->avail_in = 0;
            zlibNextBuffer();
            
            // Use default alloc and free for now
            zlibStream->zalloc = NULL;
            zlibStream->zfree = NULL;
            zlibStream->opaque = NULL;
            if (inflateInit(zlibStream) != Z_OK) {
                // Should never be Z_VERSION_ERROR
                // Really shouldn't memory error either, we just deleted
                // memory of an equal amount; therefore we throw a File
                // exception so we don't pollute the throw()
                throw FileException("Decompression error: %s", zlibStream->msg);
            }
            zlibStreamInit = 1;
        }
    }
    else {
        currentOffset = startOffset;
        bytesRead = 0;
    }
}

// World file class for loading only

const char* WorldFileLoad::correctCookie = "GCSx";
const Uint32 WorldFileLoad::currentVersion[4] = {
    VER_MAJOR, VER_MINOR, VER_RELEASE, VER_BUILD,
};

WorldFileLoad::WorldFileLoad() : blocks() { start_func
    // This will only be called by WorldFile when creating a new file
    filename = NULL;
    filePtr = NULL;
    numBlocks = 0;
    scanNextBlock = 0;
}

WorldFileLoad::WorldFileLoad(const string* openFile, int mode) throw_File : blocks() { start_func
    filename = openFile;
    filePtr = new File(filename->c_str(), mode);

    try {
        filePtr->seek(0);
    
        // Check cookie
        char cookie[5];
        filePtr->read(cookie, 4);
        cookie[4] = 0;
        
        if (strcmp(correctCookie, cookie)) {
            throw FileException("File %s is not a GCSx file", filename->c_str());
        }
        
        // Read version; skip reserved
        // @TODO: Check version and throw exception
        fileVersion[0] = filePtr->readInt();
        fileVersion[1] = filePtr->readInt();
        fileVersion[2] = filePtr->readInt();
        fileVersion[3] = filePtr->readInt();
        filePtr->skip(8);
        
        // Count of blocks
        numBlocks = filePtr->readInt();
        blocks.reserve(numBlocks);
        scanNextBlock = 0;
        
        // Block headers
        for (Uint32 pos = 0; pos < numBlocks; ++pos) {
            BlockInfo header;
            
            // Read header
            header.offset = filePtr->readInt();
            header.size = filePtr->readInt();
            header.type = filePtr->readInt();
            filePtr->skip(4);
            
            // Set other members and put in vector
            header.object = NULL;
            header.header.version = 0;
            header.header.headerSize = 0;
            header.header.contentSize = 0;
            header.header.compression = BLOCKCOMPRESSION_NONE;
            blocks.push_back(header);
        }
    }
    catch (...) {
        delete filePtr;
        throw;
    }
}

WorldFileLoad::~WorldFileLoad() { start_func
    if (filePtr) delete filePtr;
}

void WorldFileLoad::scanBlocks() { start_func
    scanNextBlock = 0;
}
    
int WorldFileLoad::nextBlock(Uint32* getType, Uint32* getID) { start_func
    while (scanNextBlock < numBlocks) {
        if (blocks[scanNextBlock].type != BLOCKTYPE_UNUSED) {
            *getType = blocks[scanNextBlock].type;
            *getID = scanNextBlock++;
            return 1;
        }
        ++scanNextBlock;
    }
    
    return 0;
}

void WorldFileLoad::claimBlock(Uint32 id, LoadOnly* claimingObject) throw_File { start_func
    assert(id >= 0);
    assert(id < numBlocks);
    assert(claimingObject);
    assert(blocks[id].object == NULL);
    assert(blocks[id].type != BLOCKTYPE_UNUSED);
    assert(filePtr);
    
    // Ensure header is read
    if (!blocks[id].header.version) {
        filePtr->seek(blocks[id].offset);
        blocks[id].header.version = filePtr->readInt();
        blocks[id].header.headerSize = filePtr->readInt();
        blocks[id].header.contentSize = filePtr->readInt();
        blocks[id].header.compression = filePtr->readInt();
    }    
    
    blocks[id].object = claimingObject;
    
    // Tell object to load header (throws but with no problems)
    FileRead headerFile(filePtr, blocks[id].offset + 16, blocks[id].header.headerSize,
                        blocks[id].header.version, BLOCKCOMPRESSION_NONE);
    claimingObject->loadHeader(&headerFile);
    
    // Tell object to load content (doesn't throw; but should delete ptr for us if it does)
    FileRead* contentFile = new FileRead(filePtr,
                                         blocks[id].offset + blocks[id].header.headerSize + 16,
                                         blocks[id].header.contentSize,
                                         blocks[id].header.version,
                                         blocks[id].header.compression);
    claimingObject->loadContent(contentFile);
}

